package com.zzt.lotterysystem.service.mq;

import cn.hutool.core.date.DateUtil;
import com.zzt.lotterysystem.common.exception.ServiceException;
import com.zzt.lotterysystem.common.utils.JacksonUtil;
import com.zzt.lotterysystem.common.utils.MailUtil;
import com.zzt.lotterysystem.common.utils.SMSUtil;
import com.zzt.lotterysystem.controller.param.DrawPrizeParam;
import com.zzt.lotterysystem.dao.dataobject.ActivityPrizeDO;
import com.zzt.lotterysystem.dao.dataobject.WinningRecordDO;
import com.zzt.lotterysystem.dao.mapper.ActivityPrizeMapper;
import com.zzt.lotterysystem.dao.mapper.WinnerRecordMapper;
import com.zzt.lotterysystem.service.DrawPrizeService;
import com.zzt.lotterysystem.service.activitystatus.ActivityStatusManager;
import com.zzt.lotterysystem.service.dto.ConvertActivityStatusDTO;
import com.zzt.lotterysystem.service.enums.ActivityPrizeStatusEnum;
import com.zzt.lotterysystem.service.enums.ActivityPrizeTiersEnum;
import com.zzt.lotterysystem.service.enums.ActivityStatusEnum;
import com.zzt.lotterysystem.service.enums.ActivityUserStatusEnum;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

import static com.zzt.lotterysystem.common.config.DirectRabbitConfig.QUEUE_NAME;

/**
 * Created with IntelliJ IDEA.
 * Description
 * User: panda
 * Date: 2025-05-29
 * Time: 10:03
 */
@Component
@RabbitListener(queues = QUEUE_NAME)
public class MqReceiver {

    private final static Logger logger = LoggerFactory.getLogger(MqReceiver.class);

    private static final String VERIFICATION_CODE_TEMPLATE_CODE = "SMS_154950909";

    @Autowired
    private DrawPrizeService drawPrizeService;

    @Autowired
    private ActivityStatusManager activityStatusManager;


    @Autowired
    private ThreadPoolTaskExecutor threadPoolTaskExecutor;

    @Autowired
    private MailUtil mailUtil;

    @Autowired
    private SMSUtil smsUtil;

    @Autowired
    private ActivityPrizeMapper activityPrizeMapper;

    @Autowired
    private WinnerRecordMapper winnerRecordMapper;

    @RabbitHandler
    public void process(Map<String, String> message) throws Exception {
        // 成功接收到队列中的消息
        logger.info("MQ成功接收到消息，message:{}", JacksonUtil.writeValueAsString(message));
        String msgString = message.get("messageData");
        DrawPrizeParam param = JacksonUtil.readValue(msgString, DrawPrizeParam.class);

        // 处理抽奖的流程

        try {

            // 检验抽奖流程是否有效
            // 1、有可能前端发起两个一样的抽奖请求，对于param来说也是一样的请求
            // 2、param: 最后一个奖项：
            //         param1: 奖品完成、活动完成
            //         param2: 抛异常，回滚事务
            if(!drawPrizeService.checkDrawPrize(param)) {
                return;
            }

            // 状态扭转处理（设计模式）
            statusConvert(param);

            // 保存中奖者名单
            List<WinningRecordDO> winningRecordDOList = drawPrizeService.saveWinnerRecords(param);

            // 通知中奖者（邮箱、短信）
            // 抽奖之后的后续流程，异步（并发）处理
            syncExecute(winningRecordDOList);

        } catch (ServiceException e) {
            logger.error("处理 MQ 消息异常！{}:{}",e.getCode(), e.getMsg(), e);
            // 如果异常，需要保证事务一致性（回滚），
            // 抛出异常:消息重试（解决异常：代码bug、网络问题、服务问题）
            rollback(param);
            throw e;

        } catch (Exception e) {
            logger.error("处理 MQ 消息异常！", e);
            // 如果异常，需要保证事务一致性（回滚），
            // 抛出异常
            rollback(param);
            throw e;
        }

    }

    /**
     * 处理抽奖异常的回滚行为
     *
     * @param param
     */
    private void rollback(DrawPrizeParam param) {
        // 1. 回滚状态：活动、奖品、人员
        // 状态是否需要回滚
        if (!statusNeedRollback(param)) {
            // 不需要：return
            return;
        }

        // 需要回滚：回滚
        rollbackStatus(param);

        // 2. 回滚中奖者名单
        // 是否需要回滚
        if (!winnerNeedRollback(param)) {
            // 不需要：return
            return;
        }

        // 需要回滚：回滚
        rollbackWinner(param);

    }

    /**
     * 回滚中奖记录：删除奖品下的中奖者
     *
     * @param param
     */
    private void rollbackWinner(DrawPrizeParam param) {
        drawPrizeService.deleteRecords(param.getActivityId(), param.getPrizeId());
    }

    private boolean winnerNeedRollback(DrawPrizeParam param) {
        // 判断活动中是否存在中奖者
        int count = winnerRecordMapper.countByActivityAndPrizeId(param.getActivityId(), param.getPrizeId());
        return count > 0;
    }

    /**
     * 回滚处理活动相关状态
     *
     * @param param
     */
    private void rollbackStatus(DrawPrizeParam param) {
        ConvertActivityStatusDTO convertActivityStatusDTO = new ConvertActivityStatusDTO();
        convertActivityStatusDTO.setActivityId(param.getActivityId());
        convertActivityStatusDTO.setTargetActivityStatus(ActivityStatusEnum.RUNNING);
        convertActivityStatusDTO.setPrizeId(param.getPrizeId());
        convertActivityStatusDTO.setTargetActivityPrizeStatus(ActivityPrizeStatusEnum.INIT);
        convertActivityStatusDTO.setUserIds(param.getWinnerList().stream()
                .map(DrawPrizeParam.Winner::getUserId)
                .collect(Collectors.toList()));
        convertActivityStatusDTO.setActivityUserStatus(ActivityUserStatusEnum.INIT);
        activityStatusManager.rollbackHandleEvent(convertActivityStatusDTO);
    }

    private boolean statusNeedRollback(DrawPrizeParam param) {
        // 判断活动+奖品+人员表相关状态是否已经扭转（正常思路）
        // 扭转状态时。保证了事务的一致性，要么都扭转了，要么都没扭转（不包含活动）
        // 因此，只用判断人员/奖品是否扭转过，就能判断出状态是否全部扭转
        // 不能判断活动是否已经扭转
        // 结论：判断奖品状态是否扭转，就能判断状态是否全部扭转
        ActivityPrizeDO activityPrizeDO =
                activityPrizeMapper.selectByActivityIdAndPrizeId(param.getActivityId(), param.getPrizeId());
        // 已经扭转了，需要回滚
        return activityPrizeDO.getStatus()
                .equalsIgnoreCase(ActivityPrizeStatusEnum.COMPLETED.name());
    }

    /**
     * 并发处理抽奖后续流程
     *
     * @param winningRecordDOList
     */
    private void syncExecute(List<WinningRecordDO> winningRecordDOList) {
        // 通过线程池 threadPoolTaskExecutor
        // 拓展：加入策略模式或者其他设计模式来完成后续的异步操作

        // 短信通知
        threadPoolTaskExecutor.execute(()->sendMessage(winningRecordDOList));

        // 邮件通知
        threadPoolTaskExecutor.execute(()->sendMail(winningRecordDOList));
    }

    /**
     * 发邮件
     *
     * @param winningRecordDOList
     */
    private void sendMail(List<WinningRecordDO> winningRecordDOList) {
        if (CollectionUtils.isEmpty(winningRecordDOList)) {
            logger.info("中奖列表为空，不用发邮件！");
            return;
        }

        for (WinningRecordDO winningRecordDO : winningRecordDOList) {
            String context =  "Hi，" + winningRecordDO.getWinnerName() + "。恭喜你在"
                    + winningRecordDO.getActivityName() + "活动中获得"
                    +
                    ActivityPrizeTiersEnum.forName(winningRecordDO.getPrizeTiers()).getMessage()
                    + "：" + winningRecordDO.getPrizeName() + "。获奖时间为"
                    + DateUtil.formatTime(winningRecordDO.getWinnerTime()) + "，请尽快领取您的奖励！";
            mailUtil.sendSampleMail(winningRecordDO.getWinnerEmail(), "中奖通知", context);
        }
    }

    /**
     * 发短信
     *
     * @param winningRecordDOList
     */
    private void sendMessage(List<WinningRecordDO> winningRecordDOList) {
        if (CollectionUtils.isEmpty(winningRecordDOList)) {
            logger.info("中奖列表为空，不用发短信！");
            return;
        }
        String code = "8888";
        for (WinningRecordDO winningRecordDO : winningRecordDOList) {
            Map<String, String> map = new HashMap<>();
            map.put("code", code);
            smsUtil.sendMessage(VERIFICATION_CODE_TEMPLATE_CODE,
                    winningRecordDO.getWinnerPhoneNumber().getValue(),
                    JacksonUtil.writeValueAsString(map));
        }
    }

    /**
     * 状态扭转
     *
     * @param param
     */
    private void statusConvert(DrawPrizeParam param) {
        ConvertActivityStatusDTO convertActivityStatusDTO = new ConvertActivityStatusDTO();
        convertActivityStatusDTO.setActivityId(param.getActivityId());
        convertActivityStatusDTO.setTargetActivityStatus(ActivityStatusEnum.COMPLETED);
        convertActivityStatusDTO.setPrizeId(param.getPrizeId());
        convertActivityStatusDTO.setTargetActivityPrizeStatus(ActivityPrizeStatusEnum.COMPLETED);
        convertActivityStatusDTO.setUserIds(param.getWinnerList().stream()
                .map(DrawPrizeParam.Winner::getUserId)
                .collect(Collectors.toList()));
        convertActivityStatusDTO.setActivityUserStatus(ActivityUserStatusEnum.COMPLETED);
        activityStatusManager.handleEvent(convertActivityStatusDTO);
    }
    /*private void statusConvert(DrawPrizeParam param) {

        // 问题：
        // 1、活动状态扭转有依赖性，导致代码维护性差
        // 2. 状态扭转条件可能会拓展，当前写法，拓展性差，维护性差
        // 3. 代码的灵活性、拓展性、维护性极差
        // 解决方案：
        // 设计模式：（责任链设计模式，策略模式）

        // 活动：RUNNING-->COMPLETED  ?? 全部奖品抽完之后才改变状态
        // 奖品：INIT-->COMPLETED
        // 人员列表：INIT-->COMPLETED

        // 1 扭转奖品状态
        // 查询活动关联的奖品信息
        // 条件判断是否符合扭转奖品状态：判断当前状态是否 不是COMPLETED，如果不是，要扭转
        // 才去扭转

        // 2 扭转人员状态
        // 查询活动关联的人员信息
        // 条件判断是否符合扭转人员状态：判断当前状态是否 不是COMPLETED，如果不是，要扭转
        // 才去扭转

        // 3 扭转活动状态（必须在扭转奖品状态之后完成）
        // 查询活动信息
        // 条件判断是否符合活动扭转状态：才去改变状态
        //     判断当前状态是否 不是COMPLETED，如果不是，
        //     且全部奖品抽完之后，
        //     （且xxx状态扭转）
        // 才去扭转

        // 4 更新活动完整信息缓存
    }*/

}
